#include <string.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <arpa/inet.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <netdb.h>
#include <errno.h>
#include <limits.h>
#include <linux/types.h>
#include <signal.h>
#include <stdbool.h>

#include "elog.h"
#include "elog_file.h"
#include "jpg_server.h"
#include "cam_tele.h"
#include "dwarf_utils.h"
#include "cam_wide.h"
#include "state_manager.h"
#include "image_processor/image_processor.h"

int sd[MAX_SD_LEN];
int sd_len;

int b_init = 0;

static StateManager *state_manager = StateManager::GetInstance();

long get_timeUsec()
{
    struct timeval t;
    gettimeofday(&t, 0);
    return (long)((long)t.tv_sec * 1000 * 1000 + t.tv_usec);
}

void sendCamTeleJpg(cfd *context_fd)
{
    unsigned char *frame = NULL;
    char buffer[BUFFER_SIZE] = {0};
    int frame_size = 0;
    unsigned int frame_id = 0;
    unsigned int frame_count = 0;
    long start_time = 0;

    getJgpFrame(frame, frame_size, frame_id);
    sprintf(buffer, "HTTP/1.1 200 OK\r\n"
                    "Content-Type: image/jpeg\r\n"
                    "Content-Length: %d\r\n"
                    "\r\n",
            frame_size);

    write(context_fd->fd, buffer, strlen(buffer));
    write(context_fd->fd, frame, frame_size);
    write(context_fd->fd, buffer, strlen(buffer));

    xfree(frame);
}

void sendCamTeleStream(cfd *context_fd)
{
    unsigned char *frame = NULL;
    char buffer[BUFFER_SIZE] = {0};
    int frame_size = 0;
    unsigned int frame_id = 0;
    unsigned int frame_count = 0;
    long start_time = 0;

    sprintf(buffer, "HTTP/1.1 200 OK\r\n"
                    "Content-Type: multipart/x-mixed-replace;boundary=" BOUNDARY "\r\n"
                    "\r\n"
                    "--" BOUNDARY "\r\n");

    if (write(context_fd->fd, buffer, strlen(buffer)) < 0)
    {
        log_i("write http error");
        return;
    }

    while (1)
    {
        getJgpFrame(frame, frame_size, frame_id);

        // 帧率计算
        //  frame_count += 1;
        //  if(frame_count == 30){
        //      frame_count = 0;
        //      log_i("frame (size: %d kB), cam tele fps is: %.1f fps\n", frame_size / 1024, 30.0 / ((get_timeUsec() - start_time) * 1.0 / 1000000));
        //      start_time = get_timeUsec();
        //  }

        sprintf(buffer, "Content-Type: image/jpeg\r\n"
                        "Content-Length: %d\r\n"
                        "\r\n",
                frame_size);

        if (write(context_fd->fd, buffer, strlen(buffer)) < 0)
            break;
        if (write(context_fd->fd, frame, frame_size) < 0)
            break;
        sprintf(buffer, "\r\n--" BOUNDARY "\r\n");
        if (write(context_fd->fd, buffer, strlen(buffer)) < 0)
            break;

        xfree(frame);
    }
    xfree(frame);
}

void sendCamWideStream(cfd *context_fd)
{
    unsigned char *frame = NULL;
    char buffer[BUFFER_SIZE] = {0};
    int frame_size = 0;
    unsigned int frame_count = 0;
    long start_time = 0;

    sprintf(buffer, "HTTP/1.1 200 OK\r\n"
                    "Content-Type: multipart/x-mixed-replace;boundary=" BOUNDARY "\r\n"
                    "\r\n"
                    "--" BOUNDARY "\r\n");

    if (write(context_fd->fd, buffer, strlen(buffer)) < 0)
    {
        log_i("write http error");
        return;
    }

    while (1)
    {
        getCamWideJgpFrame(frame, frame_size);

        // 帧率计算
        //  frame_count += 1;
        //  if(frame_count == 30){
        //      frame_count = 0;
        //      log_i("frame (size: %d kB), cam wide fps is: %.1f fps\n", frame_size / 1024, 30.0 / ((get_timeUsec() - start_time) * 1.0 / 1000000));
        //      start_time = get_timeUsec();
        //  }

        sprintf(buffer, "Content-Type: image/jpeg\r\n"
                        "Content-Length: %d\r\n"
                        "\r\n",
                frame_size);

        if (write(context_fd->fd, buffer, strlen(buffer)) < 0)
            break;
        if (write(context_fd->fd, frame, frame_size) < 0)
            break;
        sprintf(buffer, "\r\n--" BOUNDARY "\r\n");
        if (write(context_fd->fd, buffer, strlen(buffer)) < 0)
            break;

        xfree(frame);
    }
    xfree(frame);
}

void *client_thread(void *arg)
{
    char buffer[BUFFER_SIZE] = {0}, *pb = buffer;
    cfd lcfd;
    char *value;

    log_i("start client thread");

    const char *filename1 = "motor_speed_x_out_file.txt"; // 删除文件
    if (std::remove(filename1) == 0)
    {
        log_i("successfully deleted motor_speed_x_out_file\r\n\r\n\r\n");
    }
    const char *filename2 = "measurement_x.txt"; // 删除文件
    if (std::remove(filename2) == 0)
    {
        log_i("successfully deleted measurement_x\r\n\r\n\r\n");
    }
    const char *filename3 = "box_center_pos_x.txt"; // 删除文件
    if (std::remove(filename3) == 0)
    {
        log_i("successfully deleted box_center_pos_x\r\n\r\n\r\n");
    }

    const char *filename4 = "motor_speed_y_out_file.txt"; // 删除文件
    if (std::remove(filename4) == 0)
    {
        log_i("successfully deleted motor_speed_y_out_file\r\n\r\n\r\n");
    }
    const char *filename5 = "measurement_y.txt"; // 删除文件
    if (std::remove(filename5) == 0)
    {
        log_i("successfully deleted measurement_y\r\n\r\n\r\n");
    }
    const char *filename6 = "box_center_pos_y.txt"; // 删除文件
    if (std::remove(filename6) == 0)
    {
        log_i("successfully deleted box_center_pos_y\r\n\r\n\r\n");
    }

    if (arg != NULL)
    {
        memcpy(&lcfd, arg, sizeof(cfd));
        xfree(arg);
    }
    else
    {
        return NULL;
    }

    memset(buffer, 0, sizeof(buffer));
    if (read(lcfd.fd, buffer, BUFFER_SIZE - 1) <= 0)
    {
        close(lcfd.fd);
        return NULL;
    }

    if ((value = strstr(pb, "GET /mainstream")) != NULL)
    {
        sendCamTeleStream(&lcfd);
    }

    if ((value = strstr(pb, "GET /secondstream")) != NULL)
    {
        sendCamWideStream(&lcfd);
    }

    if ((value = strstr(pb, "GET /main_jpg")) != NULL)
    {
        sendCamTeleJpg(&lcfd);
    }

    close(lcfd.fd);
    return NULL;
}

void *server_thread(void *arg)
{
    int port;
    int on;
    pthread_t client;
    struct addrinfo *aip, *aip2;
    struct addrinfo hints;
    struct sockaddr_storage client_addr;
    socklen_t addr_len = sizeof(struct sockaddr_storage);
    fd_set selectfds;
    int max_fds = 0;
    char name[NI_MAXHOST];
    int err;
    int i;
    int ret;

    bzero(&hints, sizeof(hints));
    hints.ai_family = PF_UNSPEC;
    hints.ai_flags = AI_PASSIVE;
    hints.ai_socktype = SOCK_STREAM;

    port = htons(8092);
    snprintf(name, sizeof(name), "%d", ntohs(port));
    if ((err = getaddrinfo(NULL, name, &hints, &aip)) != 0)
    {
        perror(gai_strerror(err));
        exit(EXIT_FAILURE);
    }

    for (i = 0; i < MAX_SD_LEN; i++)
        sd[i] = -1;

    /* open sockets for server (1 socket / address family) */
    i = 0;
    for (aip2 = aip; aip2 != NULL; aip2 = aip2->ai_next)
    {
        if ((sd[i] = socket(aip2->ai_family, aip2->ai_socktype, 0)) < 0)
        {
            continue;
        }

        /* ignore "socket already in use" errors */
        on = 1;
        if (setsockopt(sd[i], SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
        {
            perror("setsockopt(SO_REUSEADDR) failed\n");
        }

        /* IPv6 socket should listen to IPv6 only, otherwise we will get "socket already in use" */
        on = 1;
        if (aip2->ai_family == AF_INET6 && setsockopt(sd[i], IPPROTO_IPV6, IPV6_V6ONLY,
                                                      (const void *)&on, sizeof(on)) < 0)
        {
            perror("setsockopt(IPV6_V6ONLY) failed\n");
        }

        /* perhaps we will use this keep-alive feature oneday */
        /* setsockopt(sd, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof(on)); */
        if (bind(sd[i], aip2->ai_addr, aip2->ai_addrlen) < 0)
        {
            log_e("bind");
            perror("bind");
            sd[i] = -1;
            continue;
        }

        if (listen(sd[i], 10) < 0)
        {
            perror("listen");
            sd[i] = -1;
        }
        else
        {
            i++;
            if (i >= MAX_SD_LEN)
            {
                i--;
                break;
            }
        }
    }

    sd_len = i;

    if (sd_len < 1)
    {
        exit(EXIT_FAILURE);
    }

    while (1)
    {
        cfd *pcfd = (cfd *)malloc(sizeof(cfd));

        if (pcfd == NULL)
        {
            fprintf(stderr, "failed to allocate (a very small amount of) memory\n");
            exit(EXIT_FAILURE);
        }

        log_i("waiting for clients to connect");

        do
        {
            FD_ZERO(&selectfds);

            for (i = 0; i < MAX_SD_LEN; i++)
            {
                if (sd[i] != -1)
                {
                    FD_SET(sd[i], &selectfds);

                    if (sd[i] > max_fds)
                        max_fds = sd[i];
                }
            }

            err = select(max_fds + 1, &selectfds, NULL, NULL, NULL);

            if (err < 0 && errno != EINTR)
            {
                perror("select");
                exit(EXIT_FAILURE);
            }
        } while (err <= 0);

        for (i = 0; i < max_fds + 1; i++)
        {
            if (sd[i] != -1 && FD_ISSET(sd[i], &selectfds))
            {
                pcfd->fd = accept(sd[i], (struct sockaddr *)&client_addr, &addr_len);

                /* start new thread that will handle this TCP connected client */
                // printf("create thread to handle client that just established a connection\n");

                if (getnameinfo((struct sockaddr *)&client_addr, addr_len, name, sizeof(name), NULL, 0, NI_NUMERICHOST) == 0)
                {
                    // printf("serving client: %s\n", name);
                }

                // usleep(1000 * 50);

                if (pthread_create(&client, NULL, &client_thread, pcfd) != 0)
                {
                    log_e("could not launch another client thread");
                    close(pcfd->fd);
                    xfree(pcfd);
                    continue;
                }
                pthread_detach(client);
            }
        }
    }
    return NULL;
}

void startServer()
{
    pthread_t server_t;

    if (1 == b_init)
    {
        return;
    }

    if (pthread_create(&server_t, NULL, &server_thread, NULL) != 0)
    {
        log_e("init server error");
        return;
    }

    pthread_detach(server_t);
    b_init = 1;
}
